package mymyoux.signals
{
	import mymyoux.utils.Hash;

	/**
	 * Create keys linked signals
	 */
	public class MetaSignal
	{
		/**
		 * @private
		 */
		protected var _valueClasses:Array;
		/**
		 * Hash with signals linked to string key
		 */
		protected var signals:Hash;
		/**
		 * Hash with signals linked to regexp key
		 */
		protected var signalsRegexp:Hash;
		/**
		 * Creates a MetaSignal instance to dispatch value objects.
		 * @param	valueClasses Any number of class references that enable type checks in dispatch().
		 * For example, new Signal(String, uint)
		 * would allow: signal.dispatch("the Answer", 42)
		 * but not: signal.dispatch(true, 42.5)
		 * nor: signal.dispatch()
		 *
		 * NOTE: Subclasses cannot call super.apply(null, valueClasses),
		 * but this constructor has logic to support super(valueClasses).
		 */
		public function MetaSignal(...classes)
		{
			_valueClasses = classes;
			signals = new Hash();
			signalsRegexp = new Hash();
		}
		/**
		 * @copy ISignal#valueClasses()
		 */
		public function get valueClasses():Array 
		{ 
			return _valueClasses; 
		}
		
		/**
		 * @copy ISignal#numListeners()
		 */
		public function get numListeners():uint 
		{ 
			var a:Array = signals.keys;
			var p:String;
			var length:uint = 0;
			for(p in a)
			{
				length += getSignal(a[p]).numListeners;
			}
			a = signalsRegexp.keys;
			for(p in a)
			{
				length += getSignalWithRegExp(a[p]).numListeners;
			}
			return length;
		}
		
		/**
		 * Subscribes a listener for a signal.
		 * @param key Key corresponding to the signal to lesson
		 * @param	listener A function with arguments
		 * @param arguments Optionnal arguments added on the dispatch for this listener
		 * that matches the value classes dispatched by the signal.
		 * If value classes are not specified (e.g. via Signal constructor), dispatch() can be called without arguments.
		 */
		public function add(key:String, listener:Function, ...arguments):void
		{
			arguments.unshift(listener);
			getSignal(key).add.apply(this, arguments);
		}
		/**
		 * Subscribes a listener for a signal with regexp key.
		 * @param key Regexp key corresponding to the signal to lesson
		 * @param	listener A function with arguments
		 * @param arguments Optionnal arguments added on the dispatch for this listener
		 * that matches the value classes dispatched by the signal.
		 * If value classes are not specified (e.g. via Signal constructor), dispatch() can be called without arguments.
		 */
		public function addWithRegExp(key:String, listener:Function, ...arguments):void
		{
			arguments.unshift(listener);
			getSignalWithRegExp(key).add.apply(this, arguments);
		}
		/**
		 * Subscribes a one-time listener for a signal.
		 * The signal will remove the listener automatically the first time it is called,
		 * after the dispatch to all listeners is complete.
		 * @param key Key corresponding to the signal to lesson
		 * @param	listener A function with arguments
		 * @param arguments Optionnal arguments added on the dispatch for this listener
		 * that matches the value classes dispatched by the signal.
		 * If value classes are not specified (e.g. via Signal constructor), dispatch() can be called without arguments.
		 */
		public function addOnce(key:String, listener:Function, ...arguments):void
		{
			arguments.unshift(listener);
			getSignal(key).addOnce.apply(this, arguments);
		}
		/**
		 * Subscribes a one-time listener for a signal with regexp key.
		 * The signal will remove the listener automatically the first time it is called,
		 * after the dispatch to all listeners is complete.
		 * @param key Regexp key corresponding to the signal to lesson
		 * @param	listener A function with arguments
		 * @param arguments Optionnal arguments added on the dispatch for this listener
		 * that matches the value classes dispatched by the signal.
		 * If value classes are not specified (e.g. via Signal constructor), dispatch() can be called without arguments.
		 */
		public function addOnceWithRegExp(key:String, listener:Function, ...arguments):void
		{
			arguments.unshift(listener);
			getSignalWithRegExp(key).addOnce.apply(this, arguments);
		}
		/**
		 * Unsubscribes a listener from the signal.
		 * @param 	key	Key corresponding to the signal to remove
		 * @param	listener
		 */
		public function remove(key:String, listener:Function):void
		{
			if(signals.has(key))
			{
				getSignal(key).remove.call(this, listener);
			}
			if(signalsRegexp.has(key))
			{
				getSignalWithRegExp(key).remove.call(this, listener);
			}
		}
		
		/** 
		 * Remove all listeners linked to the key
		 * @param key Key. If key is null, removes all listeners
		 */
		public function removeAll(key:String = null):void
		{
			if(key != null)
			{
				if(signals.has(key))
				{
					getSignal(key).removeAll.apply(this);
				}
				if(signalsRegexp.has(key))
				{
					getSignalWithRegExp(key).removeAll.apply(this);
				}
			}else
			{
				var a:Array = signals.keys;
				var p:String;
				for(p in a)
				{
					(signals.get(a[p]) as Signal).removeAll();
				}
				a = signalsRegexp.keys;
				for(p in a)
				{
					(signalsRegexp.get(a[p]) as Signal).removeAll();
				}
			}
		}
		
		/**
		 * Dispatches an object to listeners.
		 * @param 	key Key corresponding to listeners
		 * @param	valueObjects	Any number of parameters to send to listeners. Will be type-checked against valueClasses.
		 * @throws	ArgumentError	<code>ArgumentError</code>:	valueObjects are not compatible with valueClasses.
		 */
		public function dispatch(key:String, ...valueObjects):void
		{
			getSignal(key).dispatch.apply(this, valueObjects);
			if(signalsRegexp.length > 0)
			{
				var p:String;
				var a:Array = signalsRegexp.keys;
				for(p in a)
				{
					if((new RegExp(a[p])).test(key))
					{
						(signalsRegexp.get(a[p]) as Signal).dispatch.apply(this, valueObjects);
					}
				}
			}
		}
		/**
		 * Get the signal linked to the key, if it doesn't exist it will be created. (Doesn't return regexp signal)
		 * @param key Key
		 * @return Signal linked to the key
		 */
		public function getSignal(key:String):Signal
		{
			if(!signals.has(key))
			{
				signals.put(key, new Signal(_valueClasses));
			}
			return signals.get(key) as Signal;
		}
		/**
		 * Get the signal with regexp linked to the key, if it doesn't exist it will be created.
		 * @param key Key
		 * @return Signal linked to the key
		 */
		public function getSignalWithRegExp(key:String):Signal
		{
			if(!signalsRegexp.has(key))
			{
				signalsRegexp.put(key, new Signal(_valueClasses));
			}
			return signalsRegexp.get(key) as Signal;
		}
		/**
		 * @copy mymyoux.utils.Hash#keys
		 */
		public function get keys():Array
		{
			return signals.keys.concat(signalsRegexp.keys);
		}
		/**
		 * @copy mymyoux.utils.Hash#vkeys
		 */
		public function get vkeys():Vector.<String>
		{
			return signals.vKeys.concat(signalsRegexp.vKeys);
		}
	}
}